summaryrefslogtreecommitdiff
path: root/app/api/auth/[...nextauth]/route.ts
blob: 969263ea8f1b6d0d2714c8d725fdab3d771f530b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
// (1) next-auth에서 필요한 타입들을 import
import NextAuth, {
  NextAuthOptions,         // authOptions에 쓸 타입
  Session,
  User
} from 'next-auth'
import { JWT } from "next-auth/jwt"

import CredentialsProvider from 'next-auth/providers/credentials'

import { verifyExternalCredentials, verifyOtp, verifyOtpTemp } from '@/lib/users/verifyOtp'
import { SAMLProvider } from './saml/provider'

// 1) 모듈 보강 선언
declare module "next-auth" {
  /**
   * Session 객체를 확장
   */
  interface Session {
    user: {
      /** 우리가 필요로 하는 user id */
      id: string

      // 기본적으로 NextAuth가 제공하는 name/email/image 필드
      name?: string | null
      email?: string | null
      image?: string | null
      companyId?: number | null
      techCompanyId?: number | null
      domain?: string | null

    }
  }

  /**
   * User 객체를 확장
   */
  interface User {
    id: string
    imageUrl?: string | null
    companyId?: number | null
    techCompanyId?: number | null
    domain?: string | null
    // 필요한 필드를 추가로 선언 가능
  }
}

// JWT 타입 확장
declare module "next-auth/jwt" {
  interface JWT {
    id?: string
    imageUrl?: string | null
    companyId?: number | null
    techCompanyId?: number | null
    domain?: string | null
  }
}


// (2) authOptions에 NextAuthOptions 타입 지정
export const authOptions: NextAuthOptions = {
  providers: [
    CredentialsProvider({
      name: 'Credentials',
      credentials: {
        email: { label: 'Email', type: 'text' },
        code: { label: 'OTP code', type: 'text' },
      },
      async authorize(credentials, req) {
        const { email, code } = credentials ?? {}

        // OTP 검증
        const user = await verifyOtpTemp(email ?? '')
        if (!user) {
          return null
        }

        return {
          id: String(user.id ?? email ?? "dts"),
          email: user.email,
          imageUrl: user.imageUrl ?? null,
          name: user.name,   // DB에서 가져온 실제 이름
          companyId: user.companyId,   // DB에서 가져온 실제 이름
          techCompanyId: user.techCompanyId as number | undefined,   // techVendor ID
          domain: user.domain,   // DB에서 가져온 실제 이름
        }
      },
    }),
    // 새로 추가할 ID/비밀번호 provider
    CredentialsProvider({
      id: 'credentials-password',
      name: 'Username Password',
      credentials: {
        username: { label: "Username", type: "text" },
        password: { label: "Password", type: "password" }
      },
      async authorize(credentials, req) { // req 매개변수 추가
        if (!credentials?.username || !credentials?.password) {
          return null;
        }
        
        try {
          // 여기서 외부 서비스 API를 호출하여 사용자 인증
          const user = await verifyExternalCredentials(
            credentials.username,
            credentials.password
          );
          
          if (user) {
            return {
              id: String(user.id), // id를 string으로 변환
              name: user.name,
              email: user.email,
              // 첫 번째 provider와 동일한 필드 구조 유지
              imageUrl: user.imageUrl ?? null,
              companyId: user.companyId,
              techCompanyId: user.techCompanyId,
              domain: user.domain
            };
          }
          return null;
        } catch (error) {
          console.error("Authentication error:", error);
          return null;
        }
      }
    }),
    // SAML Provider 추가 (CredentialsProvider 기반)
    SAMLProvider({
      id: "credentials-saml",
      name: "SAML SSO",
      idp: {
        sso_login_url: process.env.SAML_IDP_SSO_URL!,
        sso_logout_url: process.env.SAML_IDP_SLO_URL || '', // 선택적
        certificates: [process.env.SAML_IDP_CERT!]
      },
      sp: {
        entity_id: process.env.SAML_SP_ENTITY_ID!,
        private_key: process.env.SAML_SP_PRIVATE_KEY || '',
        certificate: process.env.SAML_SP_CERT || '',
        assert_endpoint: process.env.SAML_SP_CALLBACK_URL || `${process.env.NEXTAUTH_URL}/api/saml/callback`
      }
    })
  ],
  // (3) session.strategy는 'jwt'가 되도록 선언
  //     필요하다면 as SessionStrategy 라고 명시해줄 수도 있음
  //     예) strategy: 'jwt' as SessionStrategy
  session: {
    strategy: 'jwt',
  },

  callbacks: {
    // (4) 콜백에서 token, user, session 등의 타입을 좀 더 명시해주고 싶다면 아래처럼 destructuring에 제네릭/타입 지정
    async jwt({ token, user }: { token: JWT; user?: User }) {
      if (user) {
        token.id = user.id
        token.email = user.email
        token.name = user.name
        token.companyId = user.companyId
        token.techCompanyId = user.techCompanyId
        token.domain = user.domain
        token.imageUrl = user.imageUrl
      }
      return token
    },
    async session({ session, token }: { session: Session; token: JWT }) {
      if (token) {
        session.user = {
          id: token.id as string,
          email: token.email as string,
          name: token.name as string,
          domain: token.domain as string,
          companyId: token.companyId as number,
          techCompanyId: token.techCompanyId as number,
          image: token.imageUrl ?? null
        }
      }
      return session
    },
    // redirect 콜백 추가
    async redirect({ url, baseUrl }) {
      // 상대 경로인 경우 baseUrl을 기준으로 함
      if (url.startsWith("/")) {
        return `${baseUrl}${url}`;
      }
      // 같은 도메인인 경우 그대로 사용
      else if (new URL(url).origin === baseUrl) {
        return url;
      }
      // 그 외에는 baseUrl로 리다이렉트
      return baseUrl;
    },
  },
}

const handler = NextAuth(authOptions)

export { handler as GET, handler as POST }